package casper.alpha.utils;

import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;

public class CasperUtil {
	
	private static List<Geometry> mergeGeometriesRecursively(List<Geometry> gs) {
		List<Geometry> results = new LinkedList<Geometry>();
		List<Geometry> smallerList = new LinkedList<Geometry>();
		int[] minIndices = new int[gs.size()];
		double [] minAreas = new double[gs.size()];
		// calculate minAreas
		for(int i = 0; i < gs.size(); i++) {
			double minArea = Double.MAX_VALUE;
			int minIndex = -1;
			for(int j = 0; j < gs.size(); j++) {
				if(j == i) {
					continue;
				}
				Geometry ga = gs.get(i);
				Geometry gb = gs.get(j);
				double area = ga.intersection(gb).getArea();
				if(area < minArea) {
					minArea = area;
					minIndex = j;
				}
			}
			minIndices[i] = minIndex;
			minAreas[i] = minArea;
		}
		// sort minAreas
		if(smallerList.size() < 2 || smallerList.size() == gs.size()) {
			// cannot merge
		} else {
			results = mergeGeometriesRecursively(smallerList);
		}
		return results;
	}
	
	public static List<Geometry> mergeGeometries(List<Geometry> gs) {
		List<Geometry> results = new LinkedList<Geometry>();
		results = mergeGeometriesRecursively(gs);
		return results;
	}
	
	public static Geometry bigBound(Geometry g) {
		return g.getEnvelope();
	}
	public static Geometry reduceGeometry(Geometry g, double epsilon) {
		List<Coordinate> toRemove = new LinkedList<Coordinate>();
		Coordinate[] coordinates =  g.getCoordinates();
		//System.out.println("original size : " + coordinates.length);
 		Queue<Coordinate[]> processingQueue = new LinkedList<Coordinate[]>();
 		processingQueue.add(coordinates);
 		while(!processingQueue.isEmpty()) {
 			Coordinate[] processingLine = processingQueue.poll();
 			if(processingLine.length <= 4) {
 				continue;
 			}
 			LineSegment ends = new LineSegment(processingLine[0], processingLine[processingLine.length - 1]);
 			double maxDistance = -1;
 			int maxCoordinatePos = -1;
 			for (int i = 1; i < processingLine.length - 1; i++) {
 				double distance = ends.distance(processingLine[i]);
 				if (distance > maxDistance) {
 					maxCoordinatePos = i;
 					maxDistance = distance;
 				}
 			}
 			if(maxDistance > epsilon) {
 				Coordinate[] line1 = new Coordinate[maxCoordinatePos + 1];
 				Coordinate[] line2 = new Coordinate[processingLine.length - maxCoordinatePos];
 				for(int i = 0; i < processingLine.length; i++) {
 					if(i > maxCoordinatePos) {
 						line2[i - maxCoordinatePos] = processingLine[i];
 					} else if (i < maxCoordinatePos) {
 						line1[i] = processingLine[i];
 					} else {
 						// i == maxCoordinatePos
 						line1[i] = processingLine[i];
 						line2[0] = processingLine[i];
 					}
 				}
 				processingQueue.add(line1);
 				processingQueue.add(line2);
 			} else {
 				for (int i = 1; i < processingLine.length - 1; i++) {
 	 				toRemove.add(processingLine[i]);
 	 			}
 			}
 		}
 		////System.out.println("to remove : " + toRemove.size());
 		List<Coordinate> reducedCoordinates = new LinkedList<Coordinate>();
 		for(Coordinate c : coordinates) {
 			if(toRemove.contains(c)) {
 				
 			} else {
 				reducedCoordinates.add(c);
 			}
 		}
 		Coordinate[] forReducedGeometry = new Coordinate[reducedCoordinates.size()];
 		for(int i = 0; i < reducedCoordinates.size(); i++) {
 			forReducedGeometry[i] = reducedCoordinates.get(i);
 		}
 		//System.out.println("reduced size : " + forReducedGeometry.length);
		return new GeometryFactory().createLineString(forReducedGeometry);
	}
	public static List<Geometry> optimallySplitGeometry(Geometry g) {
		List<Geometry> gs = new LinkedList<Geometry>();
		return gs;
	}
	private static double slope(LineSegment line) {
		return (line.p0.y - line.p1.y) / (line.p0.x - line.p1.x);
	}
	private static double slope90(LineSegment line) {
		return -1.0/slope(line);
	}
	private static boolean parallelx(LineSegment line) {
		return line.p0.x == line.p1.x;
	}
	private static boolean parallely(LineSegment line) {
		return line.p0.y == line.p1.y;
	}
	private static Coordinate endvertex(LineSegment line) {
		return line.p1;
	}
	public static Coordinate perpendicularBisector(LineSegment l0, LineSegment l1) {
		try {
			Coordinate m0 = l0.midPoint();
			////System.out.println(String.format("mid : %f, %f", m0.x, m0.y));
			double s0 = slope(l0);
			if(s0 == 0) {
				if(parallelx(l1)) {
					// x = x0
					// y = b + sx
					//return new Coordinate(l1.p0.x, b + s900 * l1.p0.x);
					Coordinate c = l0.intersection(l1);
					if((l1.p0.y < c.y && c.y < l1.p1.y)
							|| (l1.p0.y > c.y && c.y > l1.p1.y)) {
						
						return c;
					} else {
						//System.out.println("null case 1");
						//return endvertex(l1);
						return null;
					}
				} else {
					// y = y0
					// x = (y - b) / s
					//return new Coordinate((l1.p0.y - b) / s900, l1.p0.y);
					//System.out.println("null case 2");
					return null;
					//return endvertex(l1);
				}
				//return null;
			}
			double s900 = slope90(l0);
			//System.out.println(String.format("Slope 90 : %f", s900));
			if (s900 == 0) {
				//return endvertex(l1);
				return null;
			}
			// b = y - sx
			double b = m0.y - s900 * m0.x;
			//System.out.println(String.format("b : %f", s900));
			if(parallelx(l1)) {
				// x = x0
				// y = b + sx
				return new Coordinate(l1.p0.x, b + s900 * l1.p0.x);
			} else {
				// y = y0
				// x = (y - b) / s
				return new Coordinate((l1.p0.y - b) / s900, l1.p0.y);
			}
		} catch (Exception e) {
			//System.out.println("null case 3");
			return null;
			//return endvertex(l1);
		}
	}
	
	public static Geometry union(Geometry g1, Geometry g2) {
		Geometry gm = null;
		gm = g1.union(g2);
		Coordinate[] cs = gm.getCoordinates();
		List<Coordinate> lc = new LinkedList<Coordinate>();
		for(Coordinate c : cs) {
			
		}
		return gm;
	}
}
